/*
    <Dir Name="..">
        <File Name=".." MD5="..." />
    </Dir>

    http://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java
 */

import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class HumbleXML {
    static final String xmlFilename="Humble Downloader Tracking.xml";
    static final String nameAttribute="Name";
    static final String md5Attribute="MD5";

    static DocumentBuilderFactory documentBuilderFactory=null;
    static DocumentBuilder documentBuilder=null;

    static TransformerFactory transformerFactory=null;
    static Transformer transformer;

    static final SimpleDateFormat dateFormat;
    static {
        dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    final Path downloadDir;
    final Document document;
    boolean didChange;

    private static void staticInit() throws Exception {
        if (documentBuilderFactory==null) {

            documentBuilderFactory=DocumentBuilderFactory.newInstance();
            documentBuilderFactory.setIgnoringComments(true);
            documentBuilderFactory.setIgnoringElementContentWhitespace(true);
            documentBuilderFactory.setCoalescing(true);
            documentBuilderFactory.setExpandEntityReferences(true);

            documentBuilder=documentBuilderFactory.newDocumentBuilder();

            transformerFactory=TransformerFactory.newInstance();

            transformer=transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT,"yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        }
    }

    private void initDocument() {
        document.setXmlStandalone(true);
        Element root=document.getDocumentElement();
        if (root==null) {
            root=document.createElement("Dir");
            document.appendChild(root);
        }
        root.setAttribute(nameAttribute,downloadDir.getFileName().toString());
    }

    public HumbleXML(Path downloadDir) throws Exception {
        staticInit();
        didChange=false;
        this.downloadDir=downloadDir;
        File trackingFile=downloadDir.resolve(xmlFilename).toFile();
        if (trackingFile.exists()) {
            document=documentBuilder.parse(trackingFile);
        }
        else {
            document=documentBuilder.newDocument();
        }
        initDocument();
    }

    private Element getElement(String filename) {
        Node node=document.getDocumentElement().getFirstChild();
        while (node!=null) {
            if (node.getNodeType()==Node.ELEMENT_NODE)
            {
                Element element=(Element)node;
                if (element.getAttribute(nameAttribute).equals(filename)) {
                    return element;
                }
            }
            node=node.getNextSibling();
        }
        return null;
    }

    public MD5 getMD5(String filename) {
        Element element=getElement(filename);
        if (element!=null) {
            String hex=element.getAttribute(md5Attribute);
            if (!hex.equals("")) {
                return new MD5(hex);
            }
        }
        return null;
    }

    public void setMD5(String filename, MD5 md5) {
        Element element=getElement(filename);
        if (element==null) {
            element=document.createElement("File");
            element.setAttribute(nameAttribute,filename);
            document.getDocumentElement().appendChild(element);
        }
        String hex=md5.toString();
        if (!hex.equals(element.getAttribute(md5Attribute))) {
            element.setAttribute(md5Attribute,hex);
            didChange=true;
        }
    }

    private void addTimestamp() {
        Comment comment=document.createComment("Updated " + dateFormat.format(new Date()));
        document.insertBefore(comment,document.getDocumentElement());
    }

    public void commit() throws Exception {
        if (didChange) {
            System.out.print("Writing ");
            System.out.println(xmlFilename);
            addTimestamp();
            Path trackingPath=downloadDir.resolve(xmlFilename);
            Path tempPath=downloadDir.resolve(xmlFilename + ".tmp");
            DOMSource source=new DOMSource(document);
            StreamResult result=new StreamResult(tempPath.toFile());
            transformer.transform(source,result);
            Files.move(tempPath,trackingPath, StandardCopyOption.REPLACE_EXISTING);
            didChange=false;
        }
    }
}
